//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

//with a lot of help from http://svn.exactcode.de/exact-image/trunk/codecs/tiff.cc

#include <buola/image/algorithm.h>
#include <buola/image/cimageplugin.h>
#include <buola/image/canyimage.h>
#include <buola/io/cbufferstream.h>

#include <tiffio.h>

namespace buola { namespace img {

class CTIFFCodec : public CCodec
{
public:
    CTIFFCodec();
    virtual ~CTIFFCodec();

    void Read(io::PBufferStream pSrc,CAnyImage &pDst,EImageFormat pFmt,const CSize_i &pSize) final;
    void Write(const CAnyView &pSrc,io::PBufferStream pDst) final;
};

class CTIFFPlugin : public CImagePlugin
{
public:
    CTIFFPlugin()
    {
        RegisterPlugin("tif",this);
        RegisterPlugin("TIF",this);
        RegisterPlugin("tiff",this);
        RegisterPlugin("TIFF",this);
    }

    CCodec *GetInstance()
    {
        return new CTIFFCodec;
    }
};

static CTIFFPlugin sTIFFPlugin;

CTIFFCodec::CTIFFCodec()
{
}

CTIFFCodec::~CTIFFCodec()
{
}

struct CTIFFInHandle
{
    io::CBufferStream *mStream;
    off_t mStartPos;
};

struct CTIFFOutHandle
{
    io::CBufferStream *mStream;
    off_t mStartPos;
};

static tsize_t buola_tiff_in_read(void *pHandle,tdata_t pData,tsize_t pLength)
{
    CTIFFInHandle *lHandle=static_cast<CTIFFInHandle*>(pHandle);

    return lHandle->mStream->Read((uint8_t*)pData,pLength).Count();
}

static tsize_t buola_tiff_out_write(void *pHandle,tdata_t pData,tsize_t pLength)
{
    CTIFFOutHandle *lHandle=static_cast<CTIFFOutHandle*>(pHandle);

    return lHandle->mStream->Write((const uint8_t*)pData,pLength).Count();
}

static tsize_t buola_tiff_null_read_write(void* /*pHandle*/,tdata_t,tsize_t)
{
    return 0;
}

static toff_t buola_tiff_out_seek(void *pHandle,toff_t pOff,int pWhence)
{
    CTIFFOutHandle *lHandle=static_cast<CTIFFOutHandle*>(pHandle);

    switch(pWhence)
    {
    case SEEK_SET:
    default:
        return lHandle->mStream->Seek(lHandle->mStartPos+pOff,io::ESeek::ABSOLUTE);
    case SEEK_CUR:
        return lHandle->mStream->Seek(pOff,io::ESeek::RELATIVE);
    case SEEK_END:
        return lHandle->mStream->Seek(pOff,io::ESeek::END);
    }
}

static toff_t buola_tiff_in_seek(void *pHandle,toff_t pOff,int pWhence)
{
    CTIFFInHandle *lHandle=static_cast<CTIFFInHandle*>(pHandle);

    switch(pWhence)
    {
    case SEEK_SET:
    default:
        return lHandle->mStream->Seek(lHandle->mStartPos+pOff,io::ESeek::ABSOLUTE);
    case SEEK_CUR:
        return lHandle->mStream->Seek(pOff,io::ESeek::RELATIVE);
    case SEEK_END:
        return lHandle->mStream->Seek(pOff,io::ESeek::END);
    }
}

static toff_t buola_tiff_out_size(void *pHandle)
{
    CTIFFOutHandle *lHandle=static_cast<CTIFFOutHandle*>(pHandle);

    off_t lPos=lHandle->mStream->Tell();
    lHandle->mStream->Seek(0,io::ESeek::END);
    off_t lLen=lHandle->mStream->Tell()-lHandle->mStartPos;
    lHandle->mStream->Seek(lPos);

    return lLen;
}

static toff_t buola_tiff_in_size(void *pHandle)
{
    CTIFFInHandle *lHandle=static_cast<CTIFFInHandle*>(pHandle);

    off_t lPos=lHandle->mStream->Tell();
    lHandle->mStream->Seek(0,io::ESeek::END);
    off_t lLen=lHandle->mStream->Tell()-lHandle->mStartPos;
    lHandle->mStream->Seek(lPos);

    return lLen;
}

static int buola_tiff_close(void* /*pHandle*/)
{
    return 0;
}

static int buola_tiff_map(void* /*pHandle*/,tdata_t*,toff_t*)
{
    return 0;
}

static void buola_tiff_unmap(void* /*pHandle*/,tdata_t,toff_t)
{
}

void CTIFFCodec::Read(io::PBufferStream pSrc,CAnyImage &pDst,EImageFormat pFmt,const CSize_i& /*pSize*/)
{
    CTIFFInHandle lHandle={pSrc.get(),pSrc->Tell()};
    
    TIFF* lTIFF=TIFFClientOpen("","rm",&lHandle,buola_tiff_in_read,buola_tiff_null_read_write,buola_tiff_in_seek,
                               buola_tiff_close,buola_tiff_in_size,buola_tiff_map,buola_tiff_unmap);

    if(!lTIFF) throw XInternal("Couldn't open TIFF file");

    ///\todo multiple images
    /*
    int lNumImages = TIFFNumberOfDirectories(lTIFF);

    if (index > 0 || index != TIFFCurrentDirectory(in))
    if (!TIFFSetDirectory(in, index)) {
      TIFFClose(in);
      return false;
    }
    */

    uint16 lPhotometric=0;
    TIFFGetField(lTIFF,TIFFTAG_PHOTOMETRIC,&lPhotometric);

/*    switch (photometric)
    {
    case PHOTOMETRIC_MINISWHITE:
    case PHOTOMETRIC_MINISBLACK:
    case PHOTOMETRIC_RGB:
    case PHOTOMETRIC_PALETTE:
        break;
    default:
        msg_info() << "TIFCodec: Unrecognized photometric: " << (int)photometric << "\n";
        TIFFClose(lTIFF);
        return false;
    }
*/
    uint32 w=0,h=0;
    TIFFGetField(lTIFF,TIFFTAG_IMAGEWIDTH,&w);
    TIFFGetField(lTIFF,TIFFTAG_IMAGELENGTH,&h);

    uint16 lSPP=0;
    TIFFGetField(lTIFF,TIFFTAG_SAMPLESPERPIXEL,&lSPP);

    uint16 lBPS=0;
    TIFFGetField(lTIFF,TIFFTAG_BITSPERSAMPLE,&lBPS);

    if(lSPP!=1||lBPS!=32||!w||!h)
    {
        TIFFClose(lTIFF);
        throw XInternal("wrong format in TIFF file");
    }

    //uint16 config;
    //TIFFGetField(in, TIFFTAG_PLANARCONFIG, &config);
    CImage_gray32f lDst(CSize_i(w,h));
    ptrdiff_t lStride=lDst.Stride();

    uint8_t *lData=lDst.RawBytes();

    for (int r=0;r<h;r++)
    {
        if(TIFFReadScanline(lTIFF,lData,r,0)<0)
            break;

        lData+=lStride;
    }

    TIFFClose(lTIFF);

    pDst=std::move(lDst);
}

void CTIFFCodec::Write(const img::CAnyView &pSrc,io::PBufferStream pDst)
{
    CTIFFOutHandle lHandle={pDst.get(),pDst->Tell()};

    TIFF* lTIFF=TIFFClientOpen("","wm",&lHandle,buola_tiff_null_read_write,buola_tiff_out_write,buola_tiff_out_seek,
                               buola_tiff_close,buola_tiff_out_size,buola_tiff_map,buola_tiff_unmap);

    if(!lTIFF)
    {       
        throw XInternal("Couldn't open TIFF file");
    }

    uint32 lRowsPerStrip=(uint32)-1;

    TIFFSetField(lTIFF,TIFFTAG_IMAGEWIDTH,pSrc.Width());
    TIFFSetField(lTIFF,TIFFTAG_IMAGELENGTH,pSrc.Height());
    TIFFSetField(lTIFF,TIFFTAG_BITSPERSAMPLE,32);
    TIFFSetField(lTIFF,TIFFTAG_SAMPLESPERPIXEL,1);
    TIFFSetField(lTIFF,TIFFTAG_PLANARCONFIG,PLANARCONFIG_CONTIG);
    TIFFSetField(lTIFF,TIFFTAG_SAMPLEFORMAT,SAMPLEFORMAT_IEEEFP);

    TIFFSetField(lTIFF,TIFFTAG_COMPRESSION,COMPRESSION_DEFLATE); //COMPRESSION_NONE also an option
    TIFFSetField(lTIFF,TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK); //?
    lRowsPerStrip=TIFFDefaultStripSize(lTIFF,lRowsPerStrip);
    TIFFSetField(lTIFF,TIFFTAG_ROWSPERSTRIP,lRowsPerStrip);

    ptrdiff_t lStride=pSrc.Stride();

    const uint8_t *lData=pSrc.RawBytes();

    for(int r=0;r<pSrc.Height();r++)
    {
        if(TIFFWriteScanline(lTIFF,const_cast<void*>((const void*)lData),r,0)<0)
            throw XInternal("error writing TIFF file");
        lData+=lStride;
    }

    TIFFWriteDirectory(lTIFF);
    TIFFClose(lTIFF);
}

/*namespace img*/ } /*namespace buola*/ }
